package services

import (
	"context"
	"encoding/json"
	"errors"
	"math/rand"
	"sanrenx.com/xyxf/app/daos"
	"sanrenx.com/xyxf/app/models"
	"sanrenx.com/xyxf/app/services/cache"
	"sanrenx.com/xyxf/pkg/gredis"
	"sanrenx.com/xyxf/pkg/logging"
	"sanrenx.com/xyxf/pkg/orm"
	"strconv"
)

/**
 * @Description: 文章服务
 */
type ArticleSrv struct{}

/**
 * @Description: 根据分类获取文章列表
 * @receiver srv
 * @param offset 起始
 * @param size 数量
 * @param categoryIds 分类IDs
 * @param exclude 是否排除
 * @param orderBy 排序方式
 * @return []*article.ArticleItem 文章列表
 * @return error 异常信息
 */
func (srv *ArticleSrv) GetPageArticlesByCategory(offset int64, size int64, categoryIds []int, exclude bool, orderBy string) (list []*models.Article, err error) {
	var (
		cacheSrv cache.ArticleCache
		dataIDs  []string
	)

	ctx := context.Background()
	//不同排序缓存key
	listKey := cacheSrv.GetZSetKey(categoryIds, exclude, orderBy)
	//热度缓存KEY 需要增加key到被缓存key中 更新热度
	if orderBy == "hot" {
		_ = gredis.ZAdd(ctx, cacheSrv.GetCacheHotKey(), listKey, float64(rand.Int()))
	}

	//从缓存中获取分页ID
	if dataIDs, err = gredis.ZRevRange(ctx, listKey, offset, offset+size-1); err != nil {
		logging.Error(err)
		return nil, err
	}

	//补救缓存
	if len(dataIDs) < int(size) {
		articleDao := daos.ArticleDao{DB: orm.DB}
		cacheCount, _ := gredis.ZCard(ctx, listKey)                              //缓存中数量
		dbCount, _ := articleDao.GetArticleCountByCategory(categoryIds, exclude) //数据库中数量
		//不统一需要同步
		if cacheCount < dbCount {
			//获取数据库中数据
			dbIds, columns, err := articleDao.GetPageArticleColumnByCategory(0, int(dbCount), categoryIds, exclude, orderBy)
			if err != nil {
				return nil, err
			}

			//空数据
			if dbIds == nil || columns == nil {
				return nil, nil
			}

			//补充到数据库
			for _, column := range columns {
				if orderBy == "hot" {
					_ = gredis.ZAdd(ctx, listKey, column.ID, float64(column.Hot))
				} else {
					createdAt, _ := column.CreatedAt.Unix()
					_ = gredis.ZAdd(ctx, listKey, column.ID, float64(createdAt))
				}
			}

			//获取当前分页数组
			if dbCount > offset {
				var end int64
				diff := dbCount - offset
				if diff > 0 {
					if diff > size {
						end = offset + size
					} else {
						end = offset + diff
					}
					//最大可获得
					dataIDs = dbIds[offset:end]
				}
			}
		}
	}

	//根据缓存ID 获取结构体信息
	for _, cacheID := range dataIDs {
		details := new(models.ArticleDetails)

		ID, _ := strconv.ParseUint(cacheID, 10, 64)
		dataKey := cacheSrv.GetDataKey(ID)
		//数据在缓存中，获取数据
		if gredis.Exist(ctx, dataKey) {
			var cacheData string
			cacheData, err = gredis.Get(ctx, dataKey)
			if err == nil {
				err = json.Unmarshal([]byte(cacheData), &details)
			}
		} else {
			//缓存中没找到，刷新缓存获取数据
			details, err = srv.RefreshCacheArticleById(ID)
		}
		//检查err
		if err != nil || details == nil {
			logging.Error(err)
			continue
		}

		//返回结构体
		list = append(list, &models.Article{
			ID:           details.ID,
			Title:        details.Title,
			Subtitle:     details.Subtitle,
			Source:       details.Source,
			Album:        details.Album,
			Hot:          details.Hot,
			CategoryID:   details.CategoryID,
			CategoryName: details.CategoryName,
			KeymanID:     details.KeymanID,
			KeymanName:   details.KeymanName,
		})
	}

	return list, nil
}

/**
 * @Description: 获取专辑收录文章列表
 * @receiver srv
 * @param id 专辑ID
 * @param offset 起始
 * @param size 数量
 * @return list 文章列表
 * @return err 异常信息
 */
func (srv *ArticleSrv) GetPageArticlesByAlbum(albumID uint64, offset int, size int) (list []*models.Article, err error) {

	var (
		album    *models.ArticleDetails
		cacheSrv cache.ArticleCache
		modelDao daos.ArticleDao
		dataIDs  []string
	)

	//获取专辑详情
	if album, err = srv.GetArticleById(albumID); err != nil {
		return nil, err
	}

	//校验是否为专辑
	if album.Album == false {
		return nil, errors.New("非专辑")
	}

	//不同排序缓存key
	listKey := cacheSrv.GetAlbumKey(albumID)
	//从缓存中获取分页ID
	ctx := context.Background()
	dataIDs, err = gredis.LRange(ctx, listKey, int64(offset), int64(offset+size-1))
	if err != nil {
		logging.Error(err)
		return nil, err
	}

	//缓存中不够 需要从数据库中取
	if len(dataIDs) < size {
		//数据库获取
		dataIDs, err = modelDao.GetPageArticleIDsByAlbum(offset, size, albumID)
		if err != nil {
			logging.Error(err)
			return nil, err
		}

		//补充数据
		if len(dataIDs) > 0 {
			_ = gredis.LTrim(ctx, listKey, 0, int64(offset))
			_ = gredis.RPush(ctx, listKey, dataIDs)
		}
	}

	//根据缓存ID 获取结构体信息
	for _, cacheID := range dataIDs {
		details := new(models.ArticleDetails)

		ID, _ := strconv.ParseUint(cacheID, 10, 64)
		dataKey := cacheSrv.GetDataKey(ID)
		//数据在缓存中，获取数据
		if gredis.Exist(ctx, dataKey) {
			var cacheData string
			cacheData, err = gredis.Get(ctx, dataKey)
			if err == nil {
				err = json.Unmarshal([]byte(cacheData), &details)
			}
		} else {
			//缓存中没找到，刷新缓存获取数据
			details, err = srv.RefreshCacheArticleById(ID)
		}
		//检查err
		if err != nil || details == nil {
			logging.Error(err)
			continue
		}

		//返回结构体
		list = append(list, &models.Article{
			ID:           details.ID,
			Title:        details.Title,
			Subtitle:     details.Subtitle,
			Source:       details.Source,
			Album:        details.Album,
			Hot:          details.Hot,
			CategoryID:   details.CategoryID,
			CategoryName: details.CategoryName,
			KeymanID:     details.KeymanID,
			KeymanName:   details.KeymanName,
		})
	}

	return list, nil
}

/**
 * @Description: 根据ID获取文章详情信息
 * @receiver srv
 * @param id 文章ID
 * @return data 文章详情
 * @return err 异常信息
 */
func (srv *ArticleSrv) GetArticleById(id uint64) (data *models.ArticleDetails, err error) {

	var cacheSrv cache.ArticleCache
	//查找Redis缓存
	dataKey := cacheSrv.GetDataKey(id)
	ctx := context.Background()
	if gredis.Exist(ctx, dataKey) {
		cacheData, err := gredis.Get(ctx, dataKey)
		if err != nil {
			logging.Info(err)
		} else {
			_ = json.Unmarshal([]byte(cacheData), &data)
			return data, nil
		}
	}

	//刷新缓存，同时获取数据
	if data, err = srv.RefreshCacheArticleById(id); err != nil {
		return nil, err
	}

	return data, nil
}

/**
 * @Description: 刷新文章缓存数据
 * @receiver srv
 * @param id 文章ID
 * @return data 文章
 * @return err 异常信息
 */
func (srv *ArticleSrv) RefreshCacheArticleById(id uint64) (data *models.ArticleDetails, err error) {

	modelDao := daos.ArticleDao{DB: orm.DB}
	//根据ID查找数据库获取文章
	if data, err = modelDao.GetArticle(id); err != nil {
		logging.Error(err)
		return nil, err
	}

	//更新缓存
	var cacheSrv cache.ArticleCache
	dataKey := cacheSrv.GetDataKey(id)
	//将Article写一个String缓存, 过期时间1小时
	ctx := context.Background()
	_ = gredis.Set(ctx, dataKey, data, cache.ArticleExpireTime)
	//更新缓存列表中 HOT 热度分值
	RefreshCacheKeyHot(cacheSrv.GetCacheHotKey(), data.ID, data.Hot)
	return data, nil
}
